/****************************************************************************
 * net/bluetooth/bt_l2cap.c
 * L2CAP handling
 *
 *   Copyright (C) 2018 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Ported from the Intel/Zephyr arduino101_firmware_source-v1.tar package
 * where the code was released with a compatible 3-clause BSD license:
 *
 *   Copyright (c) 2016, Intel Corporation
 *   All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <tinyara/config.h>

#include <string.h>
#include <errno.h>
#include <debug.h>

#include <tinyara/bluetooth/bt_hci.h>
#include <tinyara/bluetooth/bt_core.h>

#include "bt_hcicore.h"
#include "bt_conn.h"
#include "bt_l2cap.h"
#include "bt_att.h"
#include "bt_smp.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define BT_L2CAP_CONN_PARAM_ACCEPTED 0
#define BT_L2CAP_CONN_PARAM_REJECTED 1

/****************************************************************************
 * Private Data
 ****************************************************************************/

static FAR struct bt_l2cap_chan_s *g_channels;
static FAR struct bt_l2cap_chan_s *g_default;

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static uint8_t get_ident(FAR struct bt_conn_s *conn)
{
	conn->l2cap.ident++;

	/* Handle integer overflow (0 is not valid) */

	if (!conn->l2cap.ident) {
		conn->l2cap.ident++;
	}

	return conn->l2cap.ident;
}

void bt_l2cap_chan_register(FAR struct bt_l2cap_chan_s *chan)
{
	nvdbg("CID 0x%04x\n", chan->cid);

	chan->flink = g_channels;
	g_channels = chan;
}

void bt_l2cap_chan_default(FAR struct bt_l2cap_chan_s *chan)
{
	g_default = chan;
}

void bt_l2cap_connected(FAR struct bt_conn_s *conn)
{
	FAR struct bt_l2cap_chan_s *chan;

	/* Notify all registered channels of the connection event */

	for (chan = g_channels; chan; chan = chan->flink) {
		if (chan->connected != NULL) {
			chan->connected(conn, chan->context, chan->cid);
		}
	}

	/* Notify any default listener of the connection event */

	chan = g_default;
	if (chan != NULL && chan->connected != NULL) {
		chan->connected(conn, chan->context, chan->cid);
	}
}

void bt_l2cap_disconnected(FAR struct bt_conn_s *conn)
{
	FAR struct bt_l2cap_chan_s *chan;

	/* Notify all registered channels of the disconnection event */

	for (chan = g_channels; chan; chan = chan->flink) {
		if (chan->disconnected != NULL) {
			chan->disconnected(conn, chan->context, chan->cid);
		}
	}

	/* Notify any default listener of the disconnection event */

	chan = g_default;
	if (chan != NULL && chan->disconnected != NULL) {
		chan->disconnected(conn, chan->context, chan->cid);
	}
}

void bt_l2cap_encrypt_change(FAR struct bt_conn_s *conn)
{
	FAR struct bt_l2cap_chan_s *chan;

	/* Notify all registered channels of the encryption change event */

	for (chan = g_channels; chan; chan = chan->flink) {
		if (chan->encrypt_change != NULL) {
			chan->encrypt_change(conn, chan->context, chan->cid);
		}
	}

	/* Notify any default listener of the encryption change event */

	chan = g_default;
	if (chan != NULL && chan->encrypt_change != NULL) {
		chan->encrypt_change(conn, chan->context, chan->cid);
	}
}

struct bt_buf_s *bt_l2cap_create_pdu(FAR struct bt_conn_s *conn)
{
	size_t head_reserve = sizeof(struct bt_l2cap_hdr_s) + sizeof(struct bt_hci_acl_hdr_s) + g_btdev.btdev->head_reserve;

	return bt_buf_alloc(BT_ACL_OUT, NULL, head_reserve);
}

void bt_l2cap_send(FAR struct bt_conn_s *conn, uint16_t cid, FAR struct bt_buf_s *buf)
{
	FAR struct bt_l2cap_hdr_s *hdr;

	hdr = bt_buf_provide(buf, sizeof(*hdr));
	hdr->len = BT_HOST2LE16(buf->len - sizeof(*hdr));
	hdr->cid = BT_HOST2LE16(cid);

	bt_conn_send(conn, buf);
}

static void rej_not_understood(FAR struct bt_conn_s *conn, uint8_t ident)
{
	FAR struct bt_l2cap_cmd_reject_s *rej;
	FAR struct bt_l2cap_sig_hdr_s *hdr;
	FAR struct bt_buf_s *buf;

	buf = bt_l2cap_create_pdu(conn);
	if (!buf) {
		return;
	}

	hdr = bt_buf_extend(buf, sizeof(*hdr));
	hdr->code = BT_L2CAP_CMD_REJECT;
	hdr->ident = ident;
	hdr->len = BT_HOST2LE16(sizeof(*rej));

	rej = bt_buf_extend(buf, sizeof(*rej));
	rej->reason = BT_HOST2LE16(BT_L2CAP_REJ_NOT_UNDERSTOOD);

	bt_l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf);
}

static void le_conn_param_rsp(FAR struct bt_conn_s *conn, FAR struct bt_buf_s *buf)
{
	struct bt_l2cap_conn_param_rsp_s *rsp = (void *)buf->data;

	if (buf->len < sizeof(*rsp)) {
		ndbg("ERROR: Too small LE conn param rsp\n");
		return;
	}

	nvdbg("LE conn param rsp result %u\n", BT_LE162HOST(rsp->result));
}

static uint16_t le_validate_conn_params(uint16_t min, uint16_t max, uint16_t latency, uint16_t timeout)
{
	uint16_t max_latency;

	if (min > max || min < 6 || max > 3200) {
		return BT_L2CAP_CONN_PARAM_REJECTED;
	}

	if (timeout < 10 || timeout > 3200) {
		return BT_L2CAP_CONN_PARAM_REJECTED;
	}

	/* Calculation based on BT spec 4.2 [Vol3, PartA, 4.20] max_latency =
	 * ((timeout * 10)/(max * 1.25 * 2)) - 1;
	 */

	max_latency = (timeout * 4 / max) - 1;
	if (latency > 499 || latency > max_latency) {
		return BT_L2CAP_CONN_PARAM_REJECTED;
	}

	return BT_L2CAP_CONN_PARAM_ACCEPTED;
}

static void le_conn_param_update_req(FAR struct bt_conn_s *conn, uint8_t ident, FAR struct bt_buf_s *buf)
{
	FAR struct bt_l2cap_sig_hdr_s *hdr;
	FAR struct bt_l2cap_conn_param_rsp_s *rsp;
	FAR struct bt_l2cap_conn_param_req_s *req = (void *)buf->data;
	uint16_t min;
	uint16_t max;
	uint16_t latency;
	uint16_t timeout;
	uint16_t result;

	if (buf->len < sizeof(*req)) {
		ndbg("ERROR: Too small LE conn update param req\n");
		return;
	}

	if (conn->role != BT_HCI_ROLE_MASTER) {
		return;
	}

	min = BT_LE162HOST(req->min_interval);
	max = BT_LE162HOST(req->max_interval);
	latency = BT_LE162HOST(req->latency);
	timeout = BT_LE162HOST(req->timeout);

	nvdbg("min 0x%4.4x max 0x%4.4x latency: 0x%4.4x timeout: 0x%4.4x", min, max, latency, timeout);

	buf = bt_l2cap_create_pdu(conn);
	if (!buf) {
		return;
	}

	result = le_validate_conn_params(min, max, latency, timeout);

	hdr = bt_buf_extend(buf, sizeof(*hdr));
	hdr->code = BT_L2CAP_CONN_PARAM_RSP;
	hdr->ident = ident;
	hdr->len = BT_HOST2LE16(sizeof(*rsp));

	rsp = bt_buf_extend(buf, sizeof(*rsp));
	memset(rsp, 0, sizeof(*rsp));
	rsp->result = BT_HOST2LE16(result);

	bt_l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf);

	if (result == BT_L2CAP_CONN_PARAM_ACCEPTED) {
		bt_conn_le_conn_update(conn, min, max, latency, timeout);
	}
}

static void le_sig(FAR struct bt_conn_s *conn, FAR struct bt_buf_s *buf, FAR void *context, uint16_t cid)
{
	struct bt_l2cap_sig_hdr_s *hdr = (FAR void *)buf->data;
	uint16_t len;

	if (buf->len < sizeof(*hdr)) {
		ndbg("ERROR: Too small L2CAP LE signaling PDU\n");
		goto drop;
	}

	len = BT_LE162HOST(hdr->len);
	bt_buf_consume(buf, sizeof(*hdr));

	nvdbg("LE signaling code 0x%02x ident %u len %u\n", hdr->code, hdr->ident, len);

	if (buf->len != len) {
		ndbg("ERROR: L2CAP length mismatch (%u != %u)\n", buf->len, len);
		goto drop;
	}

	if (!hdr->ident) {
		ndbg("ERROR: Invalid ident value in L2CAP PDU\n");
		goto drop;
	}

	switch (hdr->code) {
	case BT_L2CAP_CONN_PARAM_RSP:
		le_conn_param_rsp(conn, buf);
		break;

	case BT_L2CAP_CONN_PARAM_REQ:
		le_conn_param_update_req(conn, hdr->ident, buf);
		break;

	default:
		nwdbg("Unknown L2CAP PDU code 0x%02x\n", hdr->code);
		rej_not_understood(conn, hdr->ident);
		break;
	}

drop:
	bt_buf_release(buf);
}

void bt_l2cap_receive(FAR struct bt_conn_s *conn, FAR struct bt_buf_s *buf)
{
	FAR struct bt_l2cap_hdr_s *hdr = (FAR void *)buf->data;
	FAR struct bt_l2cap_chan_s *chan;
	uint16_t cid;

	if (buf->len < sizeof(*hdr)) {
		ndbg("ERROR: Too small L2CAP PDU received\n");
		bt_buf_release(buf);
		return;
	}

	cid = BT_LE162HOST(hdr->cid);
	bt_buf_consume(buf, sizeof(*hdr));

	nvdbg("Packet for CID %u len %u\n", cid, buf->len);

	/* Search for a subscriber to this channel */

	for (chan = g_channels; chan != NULL; chan = chan->flink) {
		if (chan->cid == cid) {
			break;
		}
	}

	/* If there is no subscriber, then send all received frames to the default
	 * listener (if one is registered).
	 */

	if (chan == NULL) {
		chan = g_default;
	}

	if (chan == NULL) {
		nwdbg("WARNING: No subscriber to CID 0x%04x\n", cid);
		bt_buf_release(buf);
		return;
	}

	chan->receive(conn, buf, chan->context, cid);
}

void bt_l2cap_update_conn_param(FAR struct bt_conn_s *conn)
{
	FAR struct bt_l2cap_sig_hdr_s *hdr;
	FAR struct bt_l2cap_conn_param_req_s *req;
	FAR struct bt_buf_s *buf;

	/* Check if we need to update anything */

	if (conn->le_conn_interval >= LE_CONN_MIN_INTERVAL && conn->le_conn_interval <= LE_CONN_MAX_INTERVAL) {
		return;
	}

	buf = bt_l2cap_create_pdu(conn);
	if (!buf) {
		return;
	}

	hdr = bt_buf_extend(buf, sizeof(*hdr));
	hdr->code = BT_L2CAP_CONN_PARAM_REQ;
	hdr->ident = get_ident(conn);
	hdr->len = BT_HOST2LE16(sizeof(*req));

	req = bt_buf_extend(buf, sizeof(*req));
	req->min_interval = BT_HOST2LE16(LE_CONN_MIN_INTERVAL);
	req->max_interval = BT_HOST2LE16(LE_CONN_MAX_INTERVAL);
	req->latency = BT_HOST2LE16(LE_CONN_LATENCY);
	req->timeout = BT_HOST2LE16(LE_CONN_TIMEOUT);

	bt_l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf);
}

int bt_l2cap_update_conn_parameter(FAR struct bt_conn_s *conn, const struct bt_le_conn_param *param)
{
	FAR struct bt_l2cap_sig_hdr_s *hdr;
	FAR struct bt_l2cap_conn_param_req_s *req;
	FAR struct bt_buf_s *buf;

	buf = bt_l2cap_create_pdu(conn);

	hdr = bt_buf_extend(buf, sizeof(*hdr));
	hdr->code = BT_L2CAP_CONN_PARAM_REQ;
	hdr->ident = get_ident(conn);
	hdr->len = BT_HOST2LE16(sizeof(*req));

	req = bt_buf_extend(buf, sizeof(*req));
	req->min_interval = BT_HOST2LE16(param->interval_min);
	req->max_interval = BT_HOST2LE16(param->interval_max);
	req->latency = BT_HOST2LE16(param->latency);
	req->timeout = BT_HOST2LE16(param->timeout);

	bt_l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf);

	return 0;
}

int bt_l2cap_init(void)
{
	int ret;

	static struct bt_l2cap_chan_s chan = {
		.cid = BT_L2CAP_CID_LE_SIG,
		.receive = le_sig,
	};

	bt_att_initialize();

	ret = bt_smp_initialize();
	if (ret < 0) {
		return ret;
	}

	bt_l2cap_chan_register(&chan);
	return ret;
}
